package com.boot.security.service.authorization;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.boot.core.exception.UnsupportedClassException;
import com.boot.security.mapper.ResourceMapper;
import com.boot.security.model.bo.Authority;
import com.boot.security.model.po.Access;
import com.boot.security.model.po.Resource;
import com.boot.security.model.vo.SourceKey;
import com.boot.security.service.database.AccessResourceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Repository;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description 为 HTTP 请求寻找匹配的权限 TODO:高并发测试 Spring路径匹配优化
 * @date 2020/2/17 15:59
 **/
@Slf4j
@Repository
public class AccessSource implements FilterInvocationSecurityMetadataSource, MethodSecurityMetadataSource {

    private Map<AntPathRequestMatcher, Set<Access>> interfaceUrlSource;
    private Map<AntPathRequestMatcher, Set<String>> menuUrlSource;
    private SourceKey sourceKey;
    private final ReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = reentrantReadWriteLock.readLock();
    private final Lock writeLock = reentrantReadWriteLock.writeLock();

    ResourceMapper resourceMapper;

    AccessResourceService accessResourceService;

    public AccessSource(ResourceMapper resourceMapper, @Lazy AccessResourceService accessResourceService) {
        this.resourceMapper = resourceMapper;
        this.accessResourceService = accessResourceService;
        sourceKey = SourceKey.create();
    }

    public AntPathRequestMatcher of(Resource resource) {
        switch (resource.getMethod()) {
            case 1:
                return new AntPathRequestMatcher(resource.getContent(), "GET");
            case 2:
                return new AntPathRequestMatcher(resource.getContent(), "POST");
            case 3:
                return new AntPathRequestMatcher(resource.getContent(), "PUT");
            case 4:
                return new AntPathRequestMatcher(resource.getContent(), "PATCH");
            case 5:
                return new AntPathRequestMatcher(resource.getContent(), "DELETE");
            default:
                return new AntPathRequestMatcher(resource.getContent());
        }
    }

    public SourceKey loadSource() {
        writeLock.lock();
        try {
            loadInterfaceUrlSource();
            loadMenuUrlSource();
            sourceKey = SourceKey.create();
        } finally {
            writeLock.unlock();
        }
        return sourceKey;
    }

    public void loadInterfaceUrlSource() {
        log.info("创建后端接口路径匹配规则...");
        List<Resource> resources = resourceMapper
                .selectList(Wrappers.query(Resource.builder().type(0).build()));

        Map<AntPathRequestMatcher, Set<Access>> newInterfaceUrlSource = new HashMap<>(Math.toIntExact(Math.round(resources.size() / .75 + 1)));
        resources.forEach(resource -> {
            AntPathRequestMatcher matcher = of(resource);
            Set<Access> accesses = accessResourceService.searchResourceAccess(resource.getId());
            newInterfaceUrlSource.put(matcher, accesses);
        });
        interfaceUrlSource = newInterfaceUrlSource;
        log.info("后端接口路径匹配规则创建完毕。");
    }

    public void loadMenuUrlSource() {
        log.info("创建前端菜单路径匹配规则...");
        List<Resource> resources = resourceMapper
                .selectList(Wrappers.query(Resource.builder().type(1).build()));

        Map<AntPathRequestMatcher, Set<String>> newMenuUrlSource = new HashMap<>(Math.toIntExact(Math.round(resources.size() / .75 + 1)));
        resources.forEach(resource -> {
            AntPathRequestMatcher matcher = of(resource);
            Set<String> authorities = accessResourceService.searchResourceAccess(resource.getId())
                    .stream().map(Access::getCode).collect(Collectors.toSet());
            newMenuUrlSource.put(matcher, authorities);
        });
        menuUrlSource = newMenuUrlSource;
        log.info("前端菜单路径匹配规则创建完毕。");
    }

    public SourceKey clearSource() {
        writeLock.lock();
        try {
            log.info("销毁路径匹配规则...");
            interfaceUrlSource = null;
            menuUrlSource = null;
            sourceKey = SourceKey.create();
            log.info("路径匹配规则销毁完毕。");
        } finally {
            writeLock.unlock();
        }
        return sourceKey;
    }

    public void reset() {
        writeLock.lock();
        try {
            clearSource();
            loadSource();
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description resource未被修改，覆盖更新 resource 对应的权限，如果该 resourceId 刚插入，仍然可用
     * @date 2021/7/8 22:47
     **/
    public SourceKey saveOne(Long resourceId, SourceKey sourceKey) {
        Resource resource = resourceMapper.selectById(resourceId);
        if (null != resource) {
            AntPathRequestMatcher matcher = of(resource);
            if (resource.getType() == 0) {
                Set<Access> accesses = accessResourceService.searchResourceAccess(resource.getId());

                writeLock.lock();
                try {
                    interfaceUrlSource.put(matcher, accesses);
                    if (null == sourceKey) {
                        this.sourceKey = SourceKey.create();
                    } else {
                        this.sourceKey = sourceKey;
                    }
                } finally {
                    writeLock.unlock();
                }
            } else {
                Set<String> authorities = accessResourceService.searchResourceAccess(resource.getId())
                        .stream().map(Access::getCode).collect(Collectors.toSet());

                writeLock.lock();
                try {
                    menuUrlSource.put(matcher, authorities);
                    if (null == sourceKey) {
                        this.sourceKey = SourceKey.create();
                    } else {
                        this.sourceKey = sourceKey;
                    }
                } finally {
                    writeLock.unlock();
                }
            }
        }
        return this.sourceKey;
    }

    public SourceKey saveOne(Long resourceId) {
        return saveOne(resourceId, null);
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description resource未被修改，覆盖更新 resource 对应的权限，如果该 resourceId 刚插入，仍然可用
     * @date 2021/7/8 22:47
     **/
    public SourceKey saveMulti(Collection<Long> resourceIds, SourceKey sourceKey) {
        Resource[] resources = resourceMapper.selectBatchIds(resourceIds).toArray(new Resource[0]);
        if (resources.length > 0) {
            AntPathRequestMatcher[] matchers = new AntPathRequestMatcher[resources.length];
            List<Set<Access>> accessesList = new ArrayList<>(resources.length);
            List<Set<String>> authoritiesList = new ArrayList<>(resources.length);
            for (int i = 0; i < resources.length; i++) {
                Resource resource = resources[i];
                if (null != resource) {
                    matchers[i] = of(resource);
                    if (resource.getType() == 0) {
                        Set<Access> accesses = accessResourceService.searchResourceAccess(resource.getId());
                        accessesList.set(i, accesses);
                    } else {
                        Set<String> authorities = accessResourceService.searchResourceAccess(resource.getId())
                                .stream().map(Access::getCode).collect(Collectors.toSet());
                        authoritiesList.set(i, authorities);
                    }
                }
            }

            writeLock.lock();
            try {
                for (int i = 0; i < resources.length; i++) {
                    Resource resource = resources[i];
                    if (null != resource) {
                        AntPathRequestMatcher matcher = matchers[i];
                        if (resource.getType() == 0) {
                            interfaceUrlSource.put(matcher, accessesList.get(i));
                        } else {
                            menuUrlSource.put(matcher, authoritiesList.get(i));
                        }
                    }
                }
                if (null == sourceKey) {
                    this.sourceKey = SourceKey.create();
                } else {
                    this.sourceKey = sourceKey;
                }
            } finally {
                writeLock.unlock();
            }
        }
        return this.sourceKey;
    }

    public SourceKey saveMulti(Collection<Long> resourceIds) {
        return saveMulti(resourceIds, null);
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description resource 被修改，删除旧 resource 的权限关联，增加新 resource 对应的权限
     * @date 2021/7/8 22:51
     **/
    public SourceKey updateOne(Resource oldResource, SourceKey sourceKey) {
        removeOne(oldResource);
        return saveOne(oldResource.getId());
    }

    public SourceKey updateOne(Resource oldResource) {
        return updateOne(oldResource, null);
    }


    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description resource 被删除，删除对应的权限关联
     * @date 2021/7/8 22:51
     **/
    public SourceKey removeOne(Resource resource, SourceKey sourceKey) {
        AntPathRequestMatcher matcher = of(resource);
        writeLock.lock();
        try {
            if (resource.getType() == 0) {
                interfaceUrlSource.remove(matcher);
            } else {
                menuUrlSource.remove(matcher);
            }
            if (null == sourceKey) {
                this.sourceKey = SourceKey.create();
            } else {
                this.sourceKey = sourceKey;
            }
        } finally {
            writeLock.unlock();
        }
        return this.sourceKey;
    }

    public SourceKey removeOne(Resource resource) {
        return removeOne(resource, null);
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description resource 被删除，删除对应的权限关联
     * @date 2021/7/8 22:51
     **/
    public SourceKey removeMulti(Collection<Resource> resources, SourceKey sourceKey) {
        writeLock.lock();
        try {
            resources.forEach(resource -> {
                AntPathRequestMatcher matcher = of(resource);
                if (resource.getType() == 0) {
                    interfaceUrlSource.remove(matcher);
                } else {
                    menuUrlSource.remove(matcher);
                }
            });
            if (null == sourceKey) {
                this.sourceKey = SourceKey.create();
            } else {
                this.sourceKey = sourceKey;
            }
        } finally {
            writeLock.unlock();
        }
        return this.sourceKey;
    }

    public SourceKey removeMulti(Collection<Resource> resources) {
        return removeMulti(resources, null);
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if (object instanceof FilterInvocation) {
            readLock.lock();
            try {
                FilterInvocation filterInvocation = (FilterInvocation) object;
                HttpServletRequest request = filterInvocation.getRequest();

                //遍历我们缓存好的权限数据，找到对应的url对应的权限
                for (Map.Entry<AntPathRequestMatcher, Set<Access>> entry : interfaceUrlSource.entrySet()) {
                    if (entry.getKey().matches(request)) {
                        Set<Access> accesses = entry.getValue();
                        return accesses.stream().map(Authority::new).collect(Collectors.toSet());
                    }
                }
                return null;
            } finally {
                readLock.unlock();
            }
        } else {
            throw new UnsupportedClassException(this.getClass(), object.getClass());
        }
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        loadSource();
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    public Map<AntPathRequestMatcher, Set<String>> getMenuUrlSource() {
        return menuUrlSource;
    }

    public Lock getWriteLock() {
        return writeLock;
    }

    public Lock getReadLock() {
        return readLock;
    }

    public SourceKey getSourceKey() {
        return sourceKey;
    }

    public AccessSource setSourceKey(SourceKey sourceKey) {
        this.sourceKey = sourceKey;
        return this;
    }
}
